package main;

import interfaces.GUISource;
import interfaces.hud.chat.ChatType;
import interfaces.menu.joinGame.gamesTable.PlayerWidget;

import java.awt.DisplayMode;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;

import networking.MasterServerInterface;
import networking.messages.ChatMessage;
import networking.messages.HunterRegisterMessage;
import networking.messages.JoinMessage;
import networking.messages.SimpleMessage;
import networking.messages.TimeSyncMessage;
import networking.messages.TimeSyncMessage.TimeType;
import networking.messages.createMessages.HeatSeekerCreateMessage;
import networking.messages.createMessages.HunterCreateMessage;
import networking.messages.createMessages.MSInfoMessage;
import networking.messages.createMessages.ShipCreateMessage;
import networking.messages.createMessages.ShipType;
import networking.messages.playerMessages.PlayerAckMessage;
import networking.messages.playerMessages.PlayerAddMessage;
import networking.messages.playerMessages.PlayerAwardMessage;
import networking.messages.playerMessages.PlayerKillMessage;
import networking.messages.playerMessages.PlayerMessage;
import networking.messages.playerMessages.PlayerRemoveMessage;
import networking.messages.playerMessages.PlayerUpdateMessage;
import networking.messages.playerMessages.PlayerUpdateValueType;
import networking.messages.playerMessages.TeamChangeAnswerMessage;
import networking.messages.playerMessages.TeamChangeWantMessage;
import networking.messages.syncMessages.CMUseMessage;
import networking.messages.syncMessages.NearStationChangeMessage;
import networking.messages.syncMessages.NodeSyncMessage;
import networking.messages.syncMessages.SpawnStationSyncMessage;
import networking.messages.syncMessages.UpgradeMessage;
import networking.messages.syncMessages.UpgradeType;
import networking.messages.syncMessages.ValueType;
import networking.messages.teamMessages.ProcessCreateMessage;
import networking.messages.teamMessages.ProcessUpdateMessage;
import networking.messages.teamMessages.TeamProcessMessage;
import networking.messages.teamMessages.TeamValueMessage;
import networking.messages.weaponMessages.WeaponAddMessage;
import networking.messages.weaponMessages.WeaponFireAddMessage;
import networking.messages.weaponMessages.WeaponFireRemoveMessage;
import networking.messages.weaponMessages.WeaponRemoveMessage;
import networking.messages.weaponMessages.WeaponRequestMessage;
import networking.messages.weaponMessages.WeaponMessageType;

import org.fenggui.FengGUI;
import org.fenggui.theme.DefaultTheme;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.Pbuffer;
import org.lwjgl.opengl.PixelFormat;

import logic.common.game.GameMode;
import logic.common.team.Fraction;
import logic.common.team.processes.TeamProcess.ProcessType;
import logic.common.team.teamListeners.TeamValueType;
import logic.nodes.lod.blocks.LeafBlock;
import logic.weapons.WeaponType;
import main.listeners.listenerClasses.LanguageListener;
import main.listeners.listenerClasses.SettingsListener;
import main.listeners.listenerClasses.gui.LoginEvent;
import main.listeners.listenerClasses.gui.LoginListener;
import main.listeners.listenerClasses.gui.LogoutEvent;
import main.loading.GraphicChangeProcess;
import main.loading.LoadingProcess;
import main.loading.LoadingThread;
import main.loading.MatchLoadingProcess;
import main.start.ClientStartPolicy;
import main.start.ConquestPolicy;
import main.start.DestructionPolicy;
import main.start.StartPolicy;
import main.start.TDMPolicy;
import main.threading.GameShutdownManager;
import map.asteroids.AsteroidField;

import settings.CommonSettings;
import settings.GraphicSettings;
import sound.AudioManager;

import factories.EffectFactory;
import fileHandling.ModelImporter;
import fileHandling.ProfileHandler;
import fileHandling.language.LanguageLoader;
import fileHandling.language.interfaces.AwardType;

import gameStates.BasicGameState;
import gameStates.MouseState;
import gameStates.absGamesStates.AbsMouseState;
import gameStates.absGamesStates.gui.AbsLoginState;
import gameStates.absGamesStates.gui.AbsMenuState;
import gameStates.absGamesStates.network.AbsClientState;
import gameStates.absGamesStates.network.AbsServerState;
import gameStates.gui.ConsoleState;
import gameStates.gui.FPSState;
import gameStates.gui.GraphicChangeLoadingState;
import gameStates.gui.LoadingState;
import gameStates.gui.LoginState;
import gameStates.gui.MenuState;
import gameStates.gui.MapLoadingState;
import gameStates.network.ClientState;
import gameStates.network.ServerState;
import gameStates.passes.AbsPassManagerState;
import gameStates.passes.BasicGameStatePass;
import gameStates.passes.PassManagerGameState;

import com.captiveimagination.jgn.JGN;
import com.jme.renderer.pass.BasicPassManager;
import com.jme.renderer.pass.Pass;
import com.jme.system.DisplaySystem;
import com.jme.system.GameSettings;
import com.jme.util.GameTaskQueueManager;
import com.jmex.game.StandardGame;
import com.jmex.game.state.GameState;
import com.jmex.game.state.GameStateManager;

public class InitGame extends AbsInitGame {
	
	public static int MAX_SAMPLES;
	
	/** The only {@link GameState} that is actually attached to the {@link GameStateManager}.*/
	public AbsPassManagerState passManagerState;
	
	/** The {@link Logger} that can be used to log stuff. */
	public static final String GAME_LOG = "infinitewars";
		
	/** The {@link StandardGame} instance. */
	private StandardGame game;
	
	private LoadingProcess currentLoadingProcess;
	private LoadingThread currentLoadingThread;
	
	private HashSet<String> standardGameStates;
	
	private static AbsInitGame initGame;
	
	private InitGame(AbsClientState clientState, AbsServerState serverState) {
		super(clientState, serverState);
	}
	
	@Override
	public void init(final String[] args) {
		game = new StandardGame("Infinite Wars");
		standardGameStates = new HashSet<String>();
		
		FengGUI.setTheme(new DefaultTheme());
		
		ProfileHandler.checkSelectedProfile();
		ProfileHandler.checkProfiles();
		ProfileHandler.initSettings();
		LanguageLoader.loadAllLanguageFiles();
		
		GraphicSettings graphics = GraphicSettings.get();
		
		int width = graphics.getResolution()[0];
		int height = graphics.getResolution()[1];
		
		ResolutionHandler.get().init();
		DisplayMode mode = ResolutionHandler.get().getValidMode(width, height);
		
		MAX_SAMPLES = 0;
		try {
			PixelFormat format = new PixelFormat(32, 0, 24, 8, 0);
			Pbuffer pb = new Pbuffer(width, height, format, null);
			pb.makeCurrent();
			boolean supported = GLContext.getCapabilities().GL_ARB_multisample;
			if(supported) MAX_SAMPLES = GL11.glGetInteger(GL30.GL_MAX_SAMPLES);
			pb.destroy();
		} catch(Exception e) {
			e.printStackTrace();
		}
		
		if(graphics.getAntialiasing() > MAX_SAMPLES) {
			String aa = "Off";
			if(MAX_SAMPLES > 0) aa = MAX_SAMPLES + "x";
			graphics.setAntialiasing(aa);
			ProfileHandler.changeSettings(graphics);
		}
				
		GameSettings settings = game.getSettings();
		settings.setRenderer("LWJGL");
		settings.setWidth(width);
		settings.setHeight(height);
		settings.setFullscreen(graphics.getFullscreen());
		settings.setDepth(mode.getBitDepth());
		settings.setStencilBits(4);
		settings.setFrequency(mode.getRefreshRate());
		settings.setFramerate(graphics.getFPS());
		settings.setSamples(graphics.getAntialiasing());
		settings.setMusic(false);
		settings.setSFX(false);
				
		Runtime.getRuntime().addShutdownHook(new Thread() {
		    @Override
			public void run() {
		    	exit();
		    }
		});
		
		game.start();
		
		initPassManagerState();
		
		GameShutdownManager.get().addSystemExitWithMaxDelay(5000);
		
		GUISource.init();
		initJGNClasses();
		
		GameTaskQueueManager.getManager().update(new Callable<Void>() {
			@Override
			public Void call() throws Exception {
				StartPolicy policy = getStartPolicy(args);
				boolean autoLogin = ProfileHandler.isAutoLogin();
				
				creatingMenuState = true;
				AbsLoginState loginState = new LoginState(passManagerState);
				loginState.setActive(policy == null && !autoLogin);
				addStandardGameState(loginState);
				
				AbsMenuState menuState = new MenuState(passManagerState, clientState, serverState);
				creatingMenuState = false;
				addStandardGameState(menuState);
				menuState.setActive(autoLogin && policy == null);
				
				BasicGameState bgs = new BasicGameState(passManagerState);
				addStandardGameState(bgs);
				bgs.setActive(true);
				
				ConsoleState consoleState = new ConsoleState(passManagerState);
				addStandardGameState(consoleState);
				consoleState.setActive(false);
				
				AbsMouseState mouseState = new MouseState(passManagerState);
				addStandardGameState(mouseState);
				mouseState.setActive(true);
				
				FPSState fpsState = new FPSState(passManagerState);
				addStandardGameState(fpsState);
				fpsState.setActive(CommonSettings.get().getDrawFPS());
				
				AudioManager.init(new AudioManager());
				GameStateManager.getInstance().attachChild((AudioManager)AudioManager.get());
				
				if(policy != null) policy.performStart();
				
				return null;
			}
		});
	}
	
	public static StartPolicy getStartPolicy(String[] args) {
		if(args == null || args.length == 0) return null;
		
		if(args.length == 1) return new ClientStartPolicy(args);
		if(args.length == 8) return new DestructionPolicy(args);
		if(args.length == 9) {
			GameMode gameMode = GameMode.valueOf(args[6]);
			if(gameMode == GameMode.Conquest) return new ConquestPolicy(args);
			if(gameMode == GameMode.TeamDeathmatch) return new TDMPolicy(args);
		}
		return null;
	}
	
	private void addStandardGameState(GameState gs) {
		standardGameStates.add(gs.getName());
	}
	
	/**
	 * Initialized the game. If no arguments are given only the menu will be displayed. If a game shall be loaded 
	 * immediately, the following parameters (in this order) has to be given:<br>
	 * - the name of the scenario (the directory name)<br>
	 * - the respawn time in seconds <br>
	 * - the countdown after a game has finished in seconds <br>
	 * - the max. number of players <br>
	 * - true, if bots will be enabled, false otherwise <br>
	 * - a short name for the game <br>
	 * - the game mode <br>
	 * - true, if this game is supposed to be online, false if it shall be a LAN game
	 */
	public static void main(String[] args) {
		Logger.getLogger("com.jme").setLevel(Level.SEVERE);
		Logger.getLogger("com.jmex").setLevel(Level.SEVERE);
		Logger.getLogger("gameStates.network.IDContainer").setLevel(Level.SEVERE);
		
		init(new InitGame(new ClientState(), new ServerState()), args);
		
		Logger.getLogger("com.captiveimagination.jgn").setLevel(Level.SEVERE);
	}
	
	public static void init(AbsInitGame newInstance, String[] args) {
		initGame = newInstance;
		initGame.init(args);
	}
	
	public static AbsInitGame get() { return initGame; }
	
	/**
	 * Retrieves the IP address of this machine of the local network.
	 * @return The IP address in the local network. null, if unable to find.
	 */
	@Override
	public String getThisIP() {
		try {
			Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
			while(en.hasMoreElements()) {
				NetworkInterface netInterface = en.nextElement();
				Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
				while(addresses.hasMoreElements()) {
					InetAddress address = addresses.nextElement();
					if(address instanceof Inet4Address && !address.isLoopbackAddress()) 
						return address.getHostAddress();
				}
			}
		} catch(Exception e) { e.printStackTrace(); }
		
		return null;
	}
	
	@Override
	public void logout() {
		LogoutEvent event = new LogoutEvent(ProfileHandler.getSelectedProfile());
		loginListeners.callMethod(LoginListener.USERLOGGEDOUT, event);
	}
	
	@Override
	public void login(String profileName, boolean autoLogin) {
		ProfileHandler.setSelectedProfile(profileName, autoLogin);
		if(!profileName.equals(ProfileHandler.getSelectedProfile())) {
			GraphicSettings oldSettings = GraphicSettings.copySettings();
			changeGraphicSettings(oldSettings);
			changeCommonSettings();
			changeControlSettings();
		}
		
		loginListeners.callMethod(LoginListener.USERLOGGEDIN, new LoginEvent(profileName));
	}
	
	/**
	 * Initializes a new game. Here some garbage collection of a (potential) previous game is done before the new game 
	 * is started. 
	 * @param server true, if this is supposed to be a server hosting a game. false, if this is a client connecting 
	 * to a server.
	 */
	@Override
	public void initNewGame(boolean server, GameCycle gameCycle) {		
		isServer = server;
		ingame = false;
		
		if(gameCycle.isOnline()) MasterServerInterface.registerNewGame();
		
		EffectFactory.init();
		
		MapLoadingState loadingState = new MapLoadingState(passManagerState);
		loadingState.setActive(true);
		
		String nextScenario = gameCycle.getNextScenario();
		
		currentLoadingProcess = new MatchLoadingProcess(loadingState, nextScenario, isServer, passManagerState, 
				gameCycle);
		currentLoadingThread = new LoadingThread(currentLoadingProcess);
		currentLoadingThread.start();
	}
	
	public void resetGameStates() {
		BasicPassManager manager = passManagerState.getPassManager();
		Collection<Pass> passes = new ArrayList<Pass>();
		for(int i = 0; i < manager.passes(); i++) {
			BasicGameStatePass pass = (BasicGameStatePass)manager.get(i);
			GameState gs = pass.getGameState();
			if(!standardGameStates.contains(gs.getName())) passes.add(pass);
		}
		
		for(Pass pass : passes) {
			pass.cleanUp();
			pass.clearPassStates();
			manager.remove(pass);
		}
		
		clearLanguageListeners();
		for(LanguageListener l : menuLanguageListeners) {
			addLanguageListener(l);
		}
	}
	
	protected void initPassManagerState() {
		passManagerState = new PassManagerGameState();
		GameStateManager.getInstance().attachChild(passManagerState);
		passManagerState.setActive(true);
	}
	
	@Override
	public void abortLoadingThread() {
		if(currentLoadingThread != null) currentLoadingThread.abort();
	}
	
	private void initJGNClasses() {
		JGN.register(ArrayList.class);
		JGN.register(ShipType.class);
		JGN.register(Fraction.class);
		JGN.register(GameMode.class);
		JGN.register(ValueType.class);
		JGN.register(SimpleMessage.class);
		JGN.register(JoinMessage.class);
		JGN.register(ShipCreateMessage.class);
		JGN.register(PlayerWidget.class);
		JGN.register(GameCycle.class);
		JGN.register(WeaponType.class);
		JGN.register(HunterRegisterMessage.class);
		JGN.register(HunterCreateMessage.class);
		JGN.register(WeaponMessageType.class);
		JGN.register(WeaponRemoveMessage.class);
		JGN.register(WeaponAddMessage.class);
		JGN.register(WeaponRequestMessage.class);
		JGN.register(WeaponFireAddMessage.class);
		JGN.register(NodeSyncMessage.class);
		JGN.register(WeaponFireRemoveMessage.class);
		JGN.register(PlayerUpdateValueType.class);
		JGN.register(PlayerMessage.class);
		JGN.register(PlayerAckMessage.class);
		JGN.register(PlayerAddMessage.class);
		JGN.register(PlayerRemoveMessage.class);
		JGN.register(PlayerUpdateMessage.class);
		JGN.register(SpawnStationSyncMessage.class);
		JGN.register(MSInfoMessage.class);
		JGN.register(HeatSeekerCreateMessage.class);
		JGN.register(UpgradeType.class);
		JGN.register(UpgradeMessage.class);
		JGN.register(TimeType.class);
		JGN.register(TimeSyncMessage.class);
		JGN.register(TeamChangeWantMessage.class);
		JGN.register(TeamChangeAnswerMessage.class);
		JGN.register(NearStationChangeMessage.class);
		JGN.register(CMUseMessage.class);
		JGN.register(ChatType.class);
		JGN.register(ChatMessage.class);
		JGN.register(PlayerAwardMessage.class);
		JGN.register(AwardType.class);
		JGN.register(PlayerKillMessage.class);
		JGN.register(ProcessType.class);
		JGN.register(TeamProcessMessage.class);
		JGN.register(ProcessCreateMessage.class);
		JGN.register(ProcessUpdateMessage.class);
		JGN.register(TeamValueType.class);
		JGN.register(TeamValueMessage.class);
	}
	
	@Override
	public void loadingStopped(LoadingState loadingState) {
		super.loadingStopped(loadingState);
		currentLoadingProcess = null;
		currentLoadingThread = null;
	}
	
	@Override
	public void loadingAborted(LoadingState loadingState) {
		super.loadingAborted(loadingState);
		currentLoadingProcess = null;
		currentLoadingThread = null;
	}
	
	@Override
	public void endCurrentGame() {
		getNetworkState().cleanup();
		
		if(ingame) changeIngame(false);
		gameDestroyed();
		
		LeafBlock.allLeafBlocks.clear();
		AsteroidField.allAsteroids.clear();
		
		resetGameStates();
		
		ModelImporter.clearModelCache();
		
		currentMap = null;
	}
	
	@Override
	public void changeSoundSettings() {
		settingsListeners.callMethod(SettingsListener.SOUNDSETTINGSCHANGED);
	}
	
	@Override
	public void changeGraphicSettings(GraphicSettings oldGraphics) {
		wasIngameBeforeGraphics = isIngame();
		if(wasIngameBeforeGraphics) changeIngame(false);
				
		isChangingGraphics = true;
		GraphicSettings graphics = GraphicSettings.get();
		
		if(graphics.getAntialiasing() != oldGraphics.getAntialiasing()) 
			game.getSettings().setSamples(graphics.getAntialiasing());
		if(graphics.getFPS() != oldGraphics.getFPS()) 
			game.getSettings().setFramerate(graphics.getFPS());
		
		if(resolutionChanged(oldGraphics)) {
			int width = graphics.getResolution()[0];
			int height = graphics.getResolution()[1];
			boolean fullscreen = graphics.getFullscreen();
			
			DisplayMode mode = ResolutionHandler.get().getValidMode(width, height);
			int depth = mode.getBitDepth();
			int freq = mode.getRefreshRate();
			
			DisplaySystem.getDisplaySystem().recreateWindow(width, height, depth, freq, fullscreen);
			
			GUISource.init();
		}
		
		clearLanguageListeners();
		
		settingsListeners.callMethod(SettingsListener.GRAPHICSETTINGSCHANGED);
		
		GraphicChangeLoadingState loadingState = new GraphicChangeLoadingState(passManagerState, oldGraphics);
		loadingState.setActive(true);
		
		currentLoadingProcess = new GraphicChangeProcess(loadingState);
		currentLoadingThread = new LoadingThread(currentLoadingProcess);
		currentLoadingThread.start();
	}
	
	private boolean resolutionChanged(GraphicSettings oldGraphics) {
		GraphicSettings graphics = GraphicSettings.get();
		
		if(graphics.getFullscreen() != oldGraphics.getFullscreen()) return true;
		
		String res = graphics.getResolutionString();		
		String oldRes = oldGraphics.getResolutionString();
		
		return !res.equals(oldRes);
	}
	
	@Override
	public void exit() {
		getNetworkState().cleanup();
		game.finish();
	}
}